package com.south.base.test.arithmetic;

import org.junit.Assert;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author Administrator
 * @date 2020/5/26 16:23
 */
public class ThreeSum {
    /**
     * 给定一个包含 n 个整数的数组 nums 和一个目标值 target，
     * 判断 nums 中是否存在 a + b + c 的值与 target 相等？
     * 找出所有满足条件且不重复的组合。
     */
    @Test
    public void test() {
        System.out.println(threeSum(new int[]{1, 0, -1, 0, -2, 2}, 0));
        System.out.println(threeSum(new int[]{0, 0, 0}, 0));
    }

    /**
     * 4ms
     *
     * @param nums
     * @param target
     * @return
     */
    public List<List<Integer>> threeSum(int[] nums, int target) {
        /*定义一个返回值*/
        List<List<Integer>> result = new ArrayList<>();
        /*当数组为null或元素小于4个时，直接返回*/
        if (nums == null || nums.length < 3) {
            return result;
        }
        /*对数组进行从小到大排序*/
        Arrays.sort(nums);
        /*定义3个指针k，i，h  k从0开始遍历，i从k+1开始遍历，留下h，h指向数组最大值*/
        for (int k = 0; k < nums.length - 2; k++) {
            /*当k的值与前面的值相等时忽略*/
            if (k > 0 && nums[k] == nums[k - 1]) {
                continue;
            }
            /*获取当前最小值，如果最小值比目标值大，说明后面越来越大的值根本没戏*/
            int min1 = nums[k] + nums[k + 1] + nums[k + 2];
            if (min1 > target) {
                break;
            }
            /*获取当前最大值，如果最大值比目标值小，说明后面越来越小的值根本没戏，忽略*/
            int max1 = nums[k] + nums[nums.length - 1] + nums[nums.length - 2];
            if (max1 < target) {
                continue;
            }
            int i = k + 1;
            /*当i的值与前面的值相等时忽略*/
            if (i > k + 1 && nums[i] == nums[i - 1]) {
                continue;
            }
            /*定义指针h指向数组末尾*/
            int h = nums.length - 1;

            /*开始i指针和h指针的表演，计算当前和，
                如果等于目标值，i++并去重，h--并去重，当当前和大于目标值时h--，当当前和小于目标值时i++*/
            while (i < h) {
                int curr = nums[k] + nums[i] + nums[h];
                if (curr == target) {
                    result.add(Arrays.asList(nums[k], nums[i], nums[h]));
                    i++;
                    while (i < h && nums[i] == nums[i - 1]) {
                        i++;
                    }
                    h--;
                    while (i < h && nums[h] == nums[h + 1]) {
                        h--;
                    }
                } else if (curr > target) {
                    h--;
                } else {
                    i++;

                }
            }
        }
        return result;
    }

    /**
     * 给定一个包括 n 个整数的数组 nums 和 一个目标值 target。
     * 找出 nums 中的三个整数，使得它们的和与 target 最接近。
     * 返回这三个数的和。
     * 假定每组输入只存在唯一答案。
     */
    @Test
    public void test2() {
        Assert.assertEquals(2, threeSumClosest(new int[]{-1, 2, 1, -4}, 1));
    }

    public int threeSumClosest(int[] nums, int target) {
        Arrays.sort(nums);
        int res = nums[0] + nums[1] + nums[2];
        for (int k = 0; k < nums.length - 2; k++) {
            int i = k + 1;
            int h = nums.length - 1;

            /*开始i指针和h指针的表演，计算当前和，
                如果等于目标值，i++并去重，h--并去重，当当前和大于目标值时h--，当当前和小于目标值时i++*/
            while (i < h) {
                int curr = nums[k] + nums[i] + nums[h];
                if (curr == target) {
                    return target;
                } else if (curr > target) {
                    res = Math.abs(res - target) > curr - target ? curr : res;
                    h--;
                } else {
                    res = Math.abs(res - target) > target - curr ? curr : res;
                    i++;
                }
            }
        }
        return res;
    }

}
